﻿/**

 * Copyright (c) 2015-2016, FastDev 刘强 (fastdev@163.com) & Quincy.

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Resources;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Security.Policy;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Xml;
using Microsoft.Win32;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Concurrent;
using System.Data.SqlClient;
using System.Text.RegularExpressions;

namespace OF.Moniter
{
    public class AnalysisDumpFiles
    {
        public static void Analysis()
        {
            string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Input");
            List<List<DumpItem>> dumpFileList = GetAllDumpFiles(dirPath);
            AnalysisDumpPath(dumpFileList);
        }

        private static List<List<DumpItem>> GetAllDumpFiles(string dirPath)
        {
            List<List<DumpItem>> result = new List<List<DumpItem>>();
            if (!Directory.Exists(dirPath))
            {
                Console.WriteLine(dirPath + " Not exists!");
                return result;
            }
            foreach (var file in Directory.GetFiles(dirPath))
            {
                List<DumpItem> itemList = GetDumpItemList(file);
                result.Add(itemList);   
            }
            return result;
        }

        private static List<DumpItem> GetDumpItemList(string filePath)
        {
            string fileContent = null;
            List<DumpItem> resultList = new List<DumpItem>();
            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            {
                using (StreamReader sr = new StreamReader(fs))
                {
                    fileContent = sr.ReadToEnd();
                }
            }
            int findI = fileContent.IndexOf("!!!!Callstack for Thread");
            if (findI >= 0)
            {
                fileContent = fileContent.Substring(findI, fileContent.Length - findI);
            }
            else
            {
                return resultList;
            }
            string[] threads = fileContent.Split(new string[] { "!!!!Callstack for Thread " }, StringSplitOptions.RemoveEmptyEntries);
            int lineN = 0;
            Regex reg = new Regex("^\\s*([^\\t]+)\t*?");
            for (int i1 = 0; i1 < threads.Length; i1++)
            {
                string currentThread = threads[i1];
                int thId = int.Parse(currentThread.Substring(0, currentThread.IndexOf(",")));
                string[] methodCallStack = currentThread.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
                List<DumpItem> threadItemList = new List<DumpItem>();
                for (int mI = 1; mI < methodCallStack.Length; mI++)
                { 
                    string method = methodCallStack[mI];
                    
                    if(method.IndexOf("[Internal Frame, 'M-->U']") >= 0)
                    {
                        continue;
                    }
                    else if (method.IndexOf("[Internal Frame, 'U-->M']") >= 0)
                    {
                        continue;
                    }
                    else if(method.IndexOf("[IL Method without Metadata]") >= 0)
                    {
                        continue;
                    }
                    else if (method.IndexOf("[Internal Frame, 'AD Switch']") >= 0)
                    {
                        continue;
                    }
                    else if (method.IndexOf("System.Web.Mvc.ReflectedActionDescriptor.Execute (") >= 0)
                    {
                        break;
                    }
                    else if (method.IndexOf("System.Web.HttpApplication+PipelineStepManager.ResumeSteps (") >= 0)
                    {
                        break;
                    }
                    else if (method.IndexOf("System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute") >= 0)
                    {
                        break;
                    }
                    Match match = reg.Match(method);
                    threadItemList.Add(new DumpItem
                    {
                        FileName = filePath,
                        FileIndex = 0,
                        MethodKey = match.Captures[0].Value,
                        RowNumber = 0,
                        ThreadId = thId
                    });
                }
                if (threadItemList.Count > 0)
                {
                    threadItemList.Reverse();
                    for (int thItemI = 0; thItemI < threadItemList.Count; thItemI++)
                    {
                        threadItemList[thItemI].RowNumber = thItemI;
                    }
                    resultList.AddRange(threadItemList);
                }
            }
            return resultList;
        }

        private static void Test()
        {
            int th = 0;
            int rowNumber = 0;
            List<DumpItem> list1 = new List<DumpItem>();
            list1.Add(new DumpItem { FileName = "List1", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 0 });
            list1.Add(new DumpItem { FileName = "List1", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 1 });
            list1.Add(new DumpItem { FileName = "List1", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 2 });
            list1.Add(new DumpItem { FileName = "List1", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 3 });

            th = 0;
            rowNumber = 1;
            List<DumpItem> list2 = new List<DumpItem>();
            list2.Add(new DumpItem { FileName = "list2", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 0 });
            list2.Add(new DumpItem { FileName = "list2", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 1 });
            list2.Add(new DumpItem { FileName = "list2", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 2 });

            th = 0;
            rowNumber = 0;
            List<DumpItem> list3 = new List<DumpItem>();
            list3.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 0 });
            list3.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 1 });
            list3.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 2 });
            list3.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 3 });
            list3.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 4 });
            list3.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 5 });


            th = 0;
            rowNumber = 0;
            List<DumpItem> list4 = new List<DumpItem>();
            list4.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 0 });
            list4.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 1 });
            list4.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 2 });
            list4.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (++rowNumber), RowNumber = 3 });
            list4.Add(new DumpItem { FileName = "list3", ThreadId = th, MethodKey = "Method" + (rowNumber++), RowNumber = 4 });

            List<List<DumpItem>> dumpFileLists = new List<List<DumpItem>>();
            dumpFileLists.Add(list1);
            dumpFileLists.Add(list2);
            dumpFileLists.Add(list3);
            dumpFileLists.Add(list4);

            AnalysisDumpPath(dumpFileLists);
        }

        private static void AnalysisDumpPath(List<List<DumpItem>> dumpFileLists)
        {
            List<int> fileIndexList = new List<int>();
            for (int i1 = 0; i1 < dumpFileLists.Count; i1++)
            {
                fileIndexList.Add(i1);
            }

            List<DumpItemMethodGroupKey> statDumpList = StatDumpItemList(null, dumpFileLists, 0, fileIndexList, new DiffFileSameTheadSameMethodCompare(),
                DiffFileSameTheadSameMethodSelector);
            List<List<DumpStatPath>> allPath = new List<List<DumpStatPath>>();
            List<DumpStatPath> path = new List<DumpStatPath>();
            GetAllStatPath(allPath, path, statDumpList);
            List<List<DumpStatPath>> orderList = allPath.OrderByDescending(list => DumpStatPath.GetPower(list)).ToList();


            var groupList = orderList.SelectMany(item => item).GroupBy(item => item.GroupKey.Substring(0, item.GroupKey.IndexOf(","))).OrderByDescending(item => item.Count()).ToList();
            Console.WriteLine("--------------------------Group by Method--------------------------------------");
            foreach(var item in groupList)
            {
                Console.WriteLine("CallCount " + item.Count() + ":" + item.Key);
            }
            Console.WriteLine("\n\n");

            Console.WriteLine("--------------------------Group by CallStack(OutputFormat is:{MethodName},{MethodDepth}({ThreadId}),CallCount:{CallCount}):--------------------------------------");
            for (int listI = 0; listI < orderList.Count; listI++)
            {
                var list = orderList[listI];
                Console.WriteLine("Output Method " + listI + " begin");
                var reverseList = list.ToList();
                reverseList.Reverse();
                foreach (var item in reverseList)
                {
                    Console.WriteLine("\t\t" + item.GroupKey + ",CallCount:" + item.FileCount);
                }
                Console.WriteLine("Output Method " + listI + " end");
                Console.WriteLine();
                Console.WriteLine();
            }
        }

        private static void GetAllStatPath(List<List<DumpStatPath>> allPath, List<DumpStatPath> path, List<DumpItemMethodGroupKey> statDumpList)
        {
            foreach (var item in statDumpList)
            {
                var newPath = path.ToList();
                newPath.Add(new DumpStatPath{ GroupKey = item.GroupKey, FileCount = item.FileIndexList.Count, FileIndexList = item.FileIndexList });
                if(item.NextLevelGroup == null || item.NextLevelGroup.Count == 0)
                {
                    allPath.Add(newPath);
                }
                else if (item.NextLevelGroup.Count >= 1)
                {
                    GetAllStatPath(allPath, newPath, item.NextLevelGroup);
                }
            }
        }

        public class DumpStatPath
        {
            public string GroupKey
            { get; set; }

            public int FileCount
            { get; set; }

            public List<int> FileIndexList
            { get; set; }

            public static int GetPower(List<DumpStatPath> pathList)
            {
                int power = 0;
                int powerLevel = 1;
                foreach (DumpStatPath path in pathList)
                {
                    power += powerLevel * path.FileCount;
                    powerLevel *= 2;
                }
                return power;
            }
        }

        private static List<DumpItemMethodGroupKey> StatDumpItemList(int? thId, List<List<DumpItem>> dumpFileLists, int methodDepth, List<int> fileIndexList, IEqualityComparer<DumpItem> groupCompare,
            Func<IGrouping<DumpItem, DumpItem>, DumpItemMethodGroupKey> selectGroup)
        {
            List<DumpItemMethodGroupKey>  currentList = null;
            List<DumpItem> toGroupMethodList = new List<DumpItem>();
            for (int fileIndexI = 0; fileIndexI < fileIndexList.Count; fileIndexI++)
            {
                int fileIndex = fileIndexList[fileIndexI];
                List<DumpItem> dumpFile = dumpFileLists[fileIndex];
                foreach (var currentItem in dumpFile.Where(item => (thId == null || item.ThreadId == thId.Value) && item.RowNumber == methodDepth))
                {
                    toGroupMethodList.Add(currentItem);
                    currentItem.FileIndex = fileIndex;
                }
            }

            if (toGroupMethodList.Count > 0)
            {
                currentList = toGroupMethodList.GroupBy(item => item, groupCompare).Select(item => selectGroup(item)).ToList();
                foreach (var groupKey in currentList)
                {
                    groupKey.NextLevelGroup = StatDumpItemList(groupKey.ThreadId, dumpFileLists, methodDepth + 1, groupKey.FileIndexList, groupCompare, selectGroup);
                }
            }
            return currentList;
        }
        
        public static DumpItemMethodGroupKey DiffFileSameTheadSameMethodSelector(IGrouping<DumpItem, DumpItem> group)
        {
            return new DumpItemMethodGroupKey {
                ThreadId = group.Key.ThreadId,
                GroupKey = group.Key.MethodKey + "," + group.Key.RowNumber + "(" + group.Key.ThreadId + ")",
                NextLevelGroup = null,
                FileIndexList = group.Select(item => item.FileIndex).ToList()
            };
        }

        public class DiffFileSameTheadSameMethodCompare : IEqualityComparer<DumpItem>
        {
            public bool Equals(DumpItem x, DumpItem y)
            {
                return x.ThreadId.Equals(y.ThreadId) && x.MethodKey.Equals(y.MethodKey) && x.RowNumber.Equals(y.RowNumber);
            }

            public int GetHashCode(DumpItem obj)
            {
                return obj.ThreadId.GetHashCode() ^ obj.MethodKey.GetHashCode() ^ obj.RowNumber.GetHashCode();
            }
        }

        public class DumpItemMethodGroupKey
        {
            public int ThreadId
            { get; set; }

            public string GroupKey
            { get; set; }

            public List<int> FileIndexList
            { get; set; }

            public List<DumpItemMethodGroupKey> NextLevelGroup
            { get; set; }
        }
        

        public class DumpItem
        {
            public string FileName;
            public int ThreadId;
            public string MethodKey;
            public int RowNumber;

            public int FileIndex;
        }


     

        public class WeiXinUserCustomerUserMap
        {
            public DateTime CreateDate;

            public DateTime? UpdateDate;

            public decimal D1;
            public double D2;
            public float D3;
        }
    }
}
